﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace ProLinq.Projection.Visitors.Optimization
{
	/// <summary>
	/// Modifies logical expression
	/// </summary>
	internal class LogicalExpressionVisitor : ExpressionVisitor
	{
		public Boolean RemoveNegation { get; set; }
		public Boolean Normalize { get; set; }

		protected override Expression VisitUnary(UnaryExpression node)
		{
			if (this.RemoveNegation && node.NodeType == ExpressionType.Not)
			{
				var bin = node.Operand as BinaryExpression;
				if (bin != null)
				{
					if (bin.NodeType == ExpressionType.AndAlso)
					{
						return Expression.OrElse(Expression.Not(bin.Left), Expression.Not(bin.Right));
					}
					if (bin.NodeType == ExpressionType.OrElse)
					{
						return Expression.AndAlso(Expression.Not(bin.Left), Expression.Not(bin.Right));
					}
				}
			}
			return base.VisitUnary(node);
		}


		protected override Expression VisitBinary(BinaryExpression node)
		{
			if (this.Normalize && node.NodeType == ExpressionType.AndAlso)
			{
				var leftParts = new List<Expression>();
				var rightParts = new List<Expression>();

				Expression left = this.Visit(node.Left);
				Expression right = this.Visit(node.Right);

				if (left.NodeType == ExpressionType.OrElse)
				{
					var bin = (BinaryExpression)left;
					leftParts.Add(bin.Left);
					leftParts.Add(bin.Right);
				}
				else
				{
					leftParts.Add(left);
				}

				if (right.NodeType == ExpressionType.OrElse)
				{
					var bin = (BinaryExpression)left;
					rightParts.Add(bin.Left);
					rightParts.Add(bin.Right);
				}
				else
				{
					rightParts.Add(right);
				}

				List<BinaryExpression> normilizedParts = (from lp in leftParts
														  from rp in rightParts
														  select Expression.AndAlso(lp, rp)).ToList();

				Int32 i = 0;
				Expression normalizedExpression = normilizedParts[i];
				for (; i < normilizedParts.Count; i++)
				{
					normalizedExpression = Expression.AndAlso(normalizedExpression, normilizedParts[i]);
				}

				return normalizedExpression;
			}
			return base.VisitBinary(node);
		}
	}
}
